package cluster

import (
	"context"
	"fmt"
	"sync"

	"gddgame.cc/galaxy/asteroid"
	hash2 "gddgame.cc/galaxy/asteroid/strategy/hash"
	"gddgame.cc/galaxy/utils/def"
)

type NodeConfig = string

const (
	Replicate   NodeConfig = "replicate"
	ThreadSafe  NodeConfig = "thread_safe"
	SaveLocal   NodeConfig = "save_local"
	Simple      NodeConfig = "simple"
	JoinEvent   string     = "join"
	LeaveEvent  string     = "leave"
	MasterEvent string     = "master"
	CloseEvent  string     = "close"
)

var (
	clientPool = sync.Pool{}
)

func init() {
	clientPool.New = func() interface{} {
		c := &Client{}
		c.init()
		return c
	}
}

type Service interface {
	ID() string
	Init(node Node)
	InitClient(client *Client)
	CloseInstance(node Node)
	CloseClient(client *Client)
}

type serviceBox interface {
	closeClient(client *Client, service Service)
}

type Node interface {
	def.Logger
	asteroid.Meeting

	Context() context.Context
	Container() Container
	Instance() interface{}
	Token() string
	SetInstance(instance interface{})
	SetConfig(key NodeConfig, value interface{})
	Close() error
}

type ServiceManager struct {
	services sync.Map
	clients  sync.Map
	service  Service
}

func NewManager(service Service) *ServiceManager {
	manager := &ServiceManager{
		services: sync.Map{},
		clients:  sync.Map{},
		service:  service,
	}
	return manager
}

func (manager *ServiceManager) Services(fn func(key, value interface{}) bool) {
	manager.services.Range(fn)
}

func (manager *ServiceManager) Clients(fn func(Key, value interface{}) bool) {
	manager.clients.Range(fn)
}

func (manager *ServiceManager) LoadService(identity string) (interface{}, bool) {
	return manager.services.Load(identity)
}

func (manager *ServiceManager) LoadClient(identity string) (interface{}, bool) {
	return manager.clients.Load(identity)
}

func (manager *ServiceManager) StoreService(identity string, service interface{}) {
	manager.services.Store(identity, service)
}

func (manager *ServiceManager) StoreClient(identity string, client interface{}) {
	manager.clients.Store(identity, client)
}

func (manager *ServiceManager) DeleteService(identity string) {
	manager.services.Delete(identity)
}

func (manager *ServiceManager) DeleteClient(identity string) {
	manager.clients.Delete(identity)
}

type Client struct {
	*asteroid.Room

	box       serviceBox
	service   Service
	identity  string
	container *container
	target    string
	instance  interface{}
}

func newClient(container *container, identity string, target string, box serviceBox, service Service, room *asteroid.Room) *Client {
	client := clientPool.Get().(*Client)
	client.box = box
	client.service = service
	client.identity = identity
	client.container = container
	client.target = target
	room.SetStrategy(hash2.NewStrategy())
	client.Room = room
	return client
}

func (client *Client) init() {

}

func (client *Client) Release() {
	client.instance = nil
	client.box = nil
	client.service = nil
	client.container = nil
	client.Room = nil

	client.init()
	clientPool.Put(client)
}

func (client *Client) ID() string {
	return client.identity
}

func (client *Client) Container() Container {
	return client.container
}
func (client *Client) Close() {
	client.close()
}

func (client *Client) close() {
	client.box.closeClient(client, client.service)
	client.Room.Close()
	client.Release()
}
func (client *Client) Emit() *asteroid.Pipe {
	return client.Direct(client.target)
}

func (client *Client) Exec(socket Socket, event interface{}, data ...interface{}) ([]interface{}, error) {
	r := resultPool.Get().(chan interface{})
	client.Direct(client.target).Origin(socket).CallbackTo(func(success bool, data interface{}) {
		if success {
			r <- data
		} else {
			r <- fmt.Errorf("cluster fail:%s", client.target)
		}
	}).Data(data).Send(event)
	rr := <-r
	if e, ok := rr.(error); ok {
		return nil, e
	}
	return rr.([]interface{}), nil
}

func (client *Client) Join(socket Socket, userId string) bool {
	result := make(chan bool)
	//fmt.Println("client join ", client.target)
	client.Direct(client.target).Origin(socket).List(userId).CallbackTo(func(success bool, data interface{}) {
		//fmt.Println("client join ", client.ID(), socket.ID(), userId, success)
		if success {
			_ = socket.Add(client.Room)
		}
		result <- success
	}).Send(JoinEvent)
	return <-result
}

func (client *Client) Leave(socket Socket, userId string) {
	// 如果room因为最后一个人离开，可能已经关闭了
	if client.Room != nil {
		_ = socket.Delete(client.Room)
		client.Direct(client.target).Origin(socket).List(userId).Send(LeaveEvent)
	}
}

func (client *Client) Instance() interface{} {
	return client.instance
}

func (client *Client) SetInstance(instance interface{}) {
	client.instance = instance
}

func (client *Client) changeTarget(targetId string) {
	client.target = targetId
}
